import java.util.ArrayList;

/**
 * A tile in Carcasonne.
 *
 * @author ben
 */
public class Tile {
			
	// directions
	static final public int N = 0;
	static final public int E = 1;
	static final public int S = 2;
	static final public int W = 3;
	static final public int[] ALLDIRECTIONS = {N,E,S,W};
	
	static final public int NE = 4;
	static final public int SE = 5;
	static final public int SW = 6;
	static final public int NW = 7;
	static final public int[] ALL_NEIGHBOUR_DIRECTIONS = {N,NE,E,SE,S,SW,W,NW};
	
	/// feature types
	static final public int FARM = 0;
	static final public int ROAD = 1;
	static final public int CITY = 2;
	static final public int CLOISTER = 3;
	static final public int RIVER = 3;
		
	// variables
	Board board;
	String tile;
	int i,j;
	
	// for drawing
	int x, y; // top-left coords in world space
	// 
	static int tw, th; // tile width-height in world-space
	
	// each tile has four edges [0-top, 1-right, 2-bottom, 3-left]
	// an edge is an array of features
	// for example edges[0] = {0,1,0} means that the top edge of the tile has farm, then road, then farm
	// the rotation changes the indexes into the edges, 
	//   e.g., edges[rotation] is the top edge (from the perspective of the viewer)
	int[][] edges;
	
	// each tile has a finite number of locations for meeples
	// each of these locations either has a -1 if unoccupied, 
	// or the id of the player's meeple that occupies it
	
	// rotation (*90 degrees clock-wise)
	private int rotation;
	
	
	static public class Feature
	{
		Tile tile;
		int type; // = FARM, ROAD, CITY, CLOISTER, RIVER
		int connectedTo[][]; // connectedTo[UP] = {0} --> this feature is connected to slot 0 in the up direction...
		GlobalFeature global; // which global feature is this part of?
		MeepleLocation meepleLocation; // may be =null for e.g., RIVER
		
		public Feature(Tile t, int type)
		{
			tile = t;
			this.type = type;
			connectedTo = new int[4][];
			global = null;
			meepleLocation = null;
		}
		
		/// returns true if this feature has all its connections connected
		// a global feature is complete if all its features are closed.
		public boolean isClosed()
		{
			if(this.type == Tile.CLOISTER)
			{
				// return true if all neighbouring tiles have been placed.
				for(int dir: Tile.ALL_NEIGHBOUR_DIRECTIONS)
				{
					
					int realdir = ((dir - tile.getRotation())%8 + 8)%8;
					Tile n = tile.board.getNeighbourTile(this.tile, realdir);
					if(n == null)
						return false;
				}
				
				System.out.println("Cloister is finished - release the beer swelling monk!");
				return true;
			}
			 
			for(int dir: ALLDIRECTIONS)
			{
				int realdir = ((dir - tile.getRotation())%4 + 4)%4;
				if (connectedTo[dir]!=null)
				{
					if (tile.board.getAdjacentTile(this.tile,realdir)==null) return false;					
				}
			}
			return true;
		}
	}
	
	static public class MeepleLocation
	{
		Tile tile;
		Feature feature;
		Meeple meeple;
		int type; // = FARM, ROAD, CITY, CLOISTER
		boolean used;
		int x;
		int y;		
		
		// computed worldx, worldy coordinates
		int wx, wy;
		
		public MeepleLocation(Tile t, int type, Feature feature, int x, int y)
		{
			tile = t;
			this.type = type;
			this.feature = feature;
			this.feature.meepleLocation = this;
			used = false;
			meeple = null;
			this.x = x;
			this.y = y;
		}
		
		public void addMeeple(Meeple m)
		{
			meeple = m;
			feature.global.addMeeple(m);
			
			// should we be telling meeple something?
			used = true;
		}
				
		public void removeMeeple()
		{
			if(used)
			{
				// should we be telling meeple somthing?
				// meeple.returnToOwner() ??
				meeple = null;
				used = false;				
			}
		}
		
		public double distanceFromPoint(int x, int y)
		{
			double dx = this.wx - x;
			double dy = this.wy - y;
			return Math.sqrt(dx*dx + dy*dy);

		}
		
		/** when using distance just for comparison, faster than above **/
		public double sqDistanceFromPoint(int x, int y)
		{
			double dx = this.wx - x;
			double dy = this.wy - y;
			return dx*dx + dy*dy;
		}
				
		public void computeWorldCoordinates()
		{
			float dx = x;
			float dy = y;
			dx -= Tile.tw/2;
			dy -= Tile.th/2;
			// rotate(dx,dy,-rotation*90);
			int r = tile.getRotation();
			int cosrot = r%2==1?0:r==0?1:-1;
			int sinrot = r%2==0?0:r==1?1:-1;
			
			wx = (int)(dx*cosrot + dy*sinrot) + Tile.tw/2 + tile.x;
			wy = (int)(-dx*sinrot + dy*cosrot) + Tile.th/2 + tile.y;
		}		
	}
	
	ArrayList<Feature> features;
	ArrayList<MeepleLocation> meeple_locations;
	
	public Tile(String type)
	{
		tile = type;
		board = null;
		rotation = 0;		
		edges = new int[4][];		
		features = new ArrayList<Feature>();
		meeple_locations = new ArrayList<MeepleLocation>();		
	}
	
	public int[] edge(int i)
	{
		return edges[(i+getRotation())%4];
	}
	
	public static int oppositeEdge(int i)
	{
		return (2 + i)%4;
	}
	
	public void setRotation(int r)
	{
		rotation = ((r%4)+4)%4; // handle negative rotation
	}
	public void rotate()
	{
		setRotation(getRotation()+1);
	}
	public int getRotation()
	{
		return rotation;
	}
	
	/// is it valid to join tile t to this tile's side
	public boolean canJoin(int side, Tile t)
	{
		int[] e1 = this.edge(side);
		int[] e2 = t.edge(oppositeEdge(side));
		// must match in reverse
		if (e1.length==e2.length)
		{
			for(int i=0;i<e1.length;i++)
				if (e1[i]!=e2[e1.length-i-1]) return false;
			return true;
		}
		else
			return false;
	}
	
	// given world coordinates, find closest meepleLocation
	public MeepleLocation findClosestMeepleLocation(int wx, int wy)
	{
		// find closest meeple location
		MeepleLocation closestLocation = null;
		double min_dist = 1000000;
		for (MeepleLocation l: meeple_locations)
		{
			double sqdist = l.sqDistanceFromPoint(wx, wy);
			if(sqdist < min_dist)
			{
				min_dist = sqdist;
				closestLocation = l;
			}			
		}	
		
		if (min_dist > 60) return null;
		else return closestLocation;
	}
	
	public MeepleLocation findClosestAvailableMeepleLocation(int wx, int wy)
	{
		// find closest meeple location
		MeepleLocation closestLocation = null;
		double min_dist = 1000000;
		
		for (MeepleLocation l: meeple_locations)
		{
			if (board.tryMeepleLocation(l.feature) )
			{
				double sqdist = l.sqDistanceFromPoint(wx, wy);
				if(sqdist < min_dist)
				{
					min_dist = sqdist;
					closestLocation = l;
				}
			}			
		}	
		
		if (min_dist > 60) return null;
		else return closestLocation;
	}
	
	public void placeMeeple(Tile.MeepleLocation location, Player player)
	{
		// if the tile has a meeple location
		if (location != null)
		{			
			// check to see if this meeple location is already occupied (i.e, another meeple in it's global feature)
			// if location's feature's GlobalFeature is not occupied then continue on merry way 
			
			// get a meeple from the player, which removes it from his playable stack
			Meeple meeple = player.getUnplayedMeeple();
			
			// add meeple to this location
			location.addMeeple(meeple);
	
			// set meeple stuff 
			meeple.place(this, location.feature, x, y);
		}
		else 
		{
			Debug.out.println("No meeple location found.");			
		}
	}
	
	public void placeMeeple(int x, int y, Player player)
	{
		placeMeeple(findClosestMeepleLocation(x,y),player);
	}
	
	public Feature getFeatureConnectedTo(int edge, int tag)
	{
		int aedge = (edge + getRotation())%4;
		
		//Debug.out.printf("getFeature.. checking relative edge %d\n", aedge);
		//Debug.out.print("Features: ");
		for (Feature f: features)
		{
			if (f.connectedTo[aedge]!=null)
			{
				//Debug.out.print(typeToString(f.type));
				//Debug.out.print("(");
				for (int t: f.connectedTo[aedge])
				{
					//Debug.out.print(t);
					if (t==tag) return f;
				}
				//Debug.out.print("), ");
			}
		}
		//Debug.out.println();
		return null;
	}
	
	static public String typeToString(int type)
	{
		if (type==Tile.FARM) return "Farm";
		else if (type==Tile.CITY) return "City";
		else if (type==Tile.ROAD) return "Road";
		else if (type==Tile.CLOISTER) return "Cloister";
		else if (type==Tile.RIVER) return "River";		
		else return "Unknown";
	}
	
	public void computeWorldCoordinates()
	{
		for (MeepleLocation ml: meeple_locations)
		{
			ml.computeWorldCoordinates();
		}
	}
}
